package com.immediatus.graphics.texture;

import com.immediatus.graphics.contracts.ITexture;
import com.immediatus.graphics.util.OpenGLWrapper;

import javax.microedition.khronos.opengles.GL10;
import java.io.IOException;

public abstract class Texture implements ITexture{
    private static final int[] HARDWARETEXTUREID_FETCHER = new int[1];

    protected final PixelFormat _PixelFormat;
    protected final TextureOptions _TextureOptions;
    protected final ITextureStateListener _TextureStateListener;

    protected int _HardwareTextureID = -1;
    protected boolean _LoadedToHardware;
    protected boolean _UpdateOnHardwareNeeded = false;

    public Texture(final PixelFormat pixelFormat_, final TextureOptions textureOptions_, final ITextureStateListener textureStateListener_) throws IllegalArgumentException{
        this._PixelFormat = pixelFormat_;
        this._TextureOptions = textureOptions_;
        this._TextureStateListener = textureStateListener_;
    }

    @Override
    public int getHardwareTextureID(){
        return this._HardwareTextureID;
    }

    @Override
    public boolean isLoadedToHardware(){
        return this._LoadedToHardware;
    }

    @Override
    public void setLoadedToHardware(final boolean loadedToHardware_){
        this._LoadedToHardware = loadedToHardware_;
    }

    @Override
    public boolean isUpdateOnHardwareNeeded(){
        return this._UpdateOnHardwareNeeded;
    }

    @Override
    public void setUpdateOnHardwareNeeded(final boolean updateOnHardwareNeeded_){
        this._UpdateOnHardwareNeeded = updateOnHardwareNeeded_;
    }

    public PixelFormat getPixelFormat(){
        return this._PixelFormat;
    }

    @Override
    public TextureOptions getTextureOptions(){
        return this._TextureOptions;
    }

    @Override
    public ITextureStateListener getTextureStateListener(){
        return this._TextureStateListener;
    }

    @Override
    public boolean hasTextureStateListener(){
        return this._TextureStateListener != null;
    }

    protected abstract void writeTextureToHardware(final GL10 GL_) throws IOException;

    @Override
    public void loadToHardware(final GL10 gl_) throws IOException{
        OpenGLWrapper.enableTextures(gl_);

        this.generateHardwareTextureID(gl_);
        this.bindTextureOnHardware(gl_);
        this.applyTextureOptions(gl_);
        this.writeTextureToHardware(gl_);

        this._UpdateOnHardwareNeeded = false;
        this._LoadedToHardware = true;

        if (this._TextureStateListener != null){
            this._TextureStateListener.onLoadedToHardware(this);
        }
    }

    @Override
    public void unloadFromHardware(final GL10 gl_){
        OpenGLWrapper.enableTextures(gl_);

        this.deleteTextureOnHardware(gl_);

        this._HardwareTextureID = -1;

        this._LoadedToHardware = false;

        if (this._TextureStateListener != null){
            this._TextureStateListener.onUnloadedFromHardware(this);
        }
    }

    @Override
    public void reloadToHardware(final GL10 gl_) throws IOException{
        this.unloadFromHardware(gl_);
        this.loadToHardware(gl_);
    }

    @Override
    public void bind(final GL10 gl_){
        OpenGLWrapper.bindTexture(gl_, this._HardwareTextureID);
    }

    protected void applyTextureOptions(final GL10 gl_){
        this._TextureOptions.apply(gl_);
    }

    protected void bindTextureOnHardware(final GL10 gl_){
        OpenGLWrapper.forceBindTexture(gl_, this._HardwareTextureID);
    }

    protected void deleteTextureOnHardware(final GL10 gl_){
        OpenGLWrapper.deleteTexture(gl_, this._HardwareTextureID);
    }

    protected void generateHardwareTextureID(final GL10 gl_){
        gl_.glGenTextures(1, Texture.HARDWARETEXTUREID_FETCHER, 0);

        this._HardwareTextureID = Texture.HARDWARETEXTUREID_FETCHER[0];
    }

    public enum PixelFormat{
        UNDEFINED(-1, -1, -1),
        RGBA_4444(GL10.GL_RGBA, GL10.GL_UNSIGNED_SHORT_4_4_4_4, 16),
        RGBA_5551(GL10.GL_RGBA, GL10.GL_UNSIGNED_SHORT_5_5_5_1, 16),
        RGBA_8888(GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, 32),
        RGB_565(GL10.GL_RGB, GL10.GL_UNSIGNED_SHORT_5_6_5, 16),
        A_8(GL10.GL_ALPHA, GL10.GL_UNSIGNED_BYTE, 8),
        I_8(GL10.GL_LUMINANCE, GL10.GL_UNSIGNED_BYTE, 8),
        AI_88(GL10.GL_LUMINANCE_ALPHA, GL10.GL_UNSIGNED_BYTE, 16);

        private final int _glFormat;
        private final int _glType;
        private final int _BitsPerPixel;

        private PixelFormat(final int glFormat_, final int glType_, final int bitsPerPixel_){
            this._glFormat = glFormat_;
            this._glType = glType_;
            this._BitsPerPixel = bitsPerPixel_;
        }

        public int getGLFormat(){
            return this._glFormat;
        }

        public int getGLType(){
            return this._glType;
        }

        public int getBitsPerPixel(){
            return this._BitsPerPixel;
        }
    }
}